/* ----------------------------------------------------------------------------

	pax.form.js Copyright (C) 2006, 2008 Mikkel Bergmann, Pointful

	Licence
	-------
	
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	See lgpl.txt for licence details

---------------------------------------------------------------------------- */

/*
	Script: pax.form
		This PAX library contains methods to manipulate and access forms.
		
	Author:
		Mikkel Bergmann, <http://www.pointful.com>

*/

var pax = pax || {};
pax.form = pax.form || {};

/*	Property: pax.form.ajaxSubmitFieldName
	Field name that is added to any form that PAX submits via ajax
*/
pax.form.ajaxSubmitFieldName = 'paxAjaxSubmit';

/*	Property: pax.form.submitViaIframeFieldName
	Field name that is added to any form that PAX submits using the "iframe trick"
*/
pax.form.submitViaIframeFieldName = 'paxIframeSubmit';

/*	Method: pax.form.useAjaxSubmit
	This makes a form submit via ajax, or if there are file uploads, via a hidden iframe

	Parameters:
		form - an ID or DOM element for the form we are submitting
		args - An object that contains the following:
			* callBack - Optional function to run when the submit is complete
			* url - Optionally specify a different URL to submit the form to
			* submitButton - the forms associated submit button, note that this must be bound to use ajax
*/
pax.form.useAjaxSubmit = function( form, args ) {
	args = args || {};

	//	Disable normal form submit
	pax.event.bind( form, 'submit', function(e) {
		if( e.preventDefault )e.preventDefault();	//	Most browsers
		else e.returnValue = false;					//	IE
		return false;								//	Fallback?
	} );
	
	//	Bind submit button to use ajax
	pax.event.bind( args.submitButton, 'click', function( e ) {

		//	Prevent the form from submitting the "normal" way - we must always return false as well
		if( e.preventDefault )e.preventDefault();	//	Most browsers
		else e.returnValue = false;					//	IE
			
		//	If the form has validations, (and is not valid), return false.
		if( ! pax.validate.isFormValid( form.name ) )return false;
	
		var allFields = pax.validate.allFields( form.name, true );
		var values = {};
		var hasFileUploads = false;
		for( var i = 0; i < allFields.length; i++ ) {
			var field = allFields[i];
			//	Check to see if we have file field(s), in which case we must 
			//	submit via an iframe, instead of ajax
			if( field.type == "file" ) {
				hasFileUploads = true;
				break;
			} else {
				values[field.name] = pax.form.getFieldValue( field );
			}
		}
		
		if( hasFileUploads ) {
			//	If we have file field(s), we submit via a hidden iframe
			var iFrameArgs = args;
			iFrameArgs['form'] = form;
			var url = args.url || null;
			pax.form.submitViaIframe( iFrameArgs, url );
		} else {
			//	Tell the server we submitted via ajax.
			values[pax.form.ajaxSubmitFieldName] = true;
			var targetUrl = ( args.url )? args.url: form.action;
			pax.post( targetUrl, pax.postString( values ), function( xml, txt, url ) {
				if( args.callBack )args.callBack( xml, txt, url );
			} );
		}
		
		//	Always return false, so we don't submit the form
		return false;
	} );
};


/*	Method: pax.form.submitViaIframe
	Submits a form, using a hidden iframe; this allows uploading of files, where as ajax submission doesn't

	Parameters:
		args - An object that contain the following:
			* form - an ID or DOM element for the form we are submitting
			* callBack - Optional function to run when the submit is complete
			* preserveEncoding - Optional boolean to specify that we don't want the encoding changed to 'multipart/form-data'
		url - Optionally specify a different URL to submit the form to
		
*/
pax.form.submitViaIframe = function( args, url ) {
	args = args || {};

	var id = pax.getNextId();
	
	//	Create a hidden iFrame
	var frame = document.createElement('iframe');
	frame.id = frame.name = id;
	frame.style.display = 'none';
	document.body.appendChild( frame );
	if( pax.isIe )document.frames[id].name = id;
	
	//	Get the form, and set the target to the iFrame id, plus correct encoding for file uploads
	var form = pax.$( args.form );
	if( url )form.action = url;
	form.target = id;
	form.method = 'POST';
	if( ! args.preserveEncoding )form.enctype = form.encoding = 'multipart/form-data';

	//	Add a parameter that shows we are submitting via "ajax"
	var ajaxSubmitField = pax.util.genElement( 'input', { id: pax.form.ajaxSubmitFieldName } );
	ajaxSubmitField.type = 'hidden';
	ajaxSubmitField.value = true;
	form.appendChild( ajaxSubmitField );
	
	//	Add a parameter that shows we are submitting using an iframe
	var iframeSubmitField = pax.util.genElement( 'input', { id: pax.form.submitViaIframeFieldName } );
	iframeSubmitField.type = 'hidden';
	iframeSubmitField.value = true;
	form.appendChild( iframeSubmitField );
	
	//	Bind a function to handle the frame on load.
	pax.event.bind( frame, 'load', function() {
		var result = '';
		var frameDocument = ( pax.isIe )? frame.contentWindow.document: ( frame.contentDocument || window.frames[id].document );
		if( frameDocument && frameDocument.body )result = frameDocument.body.innerHTML;
		
		//	Unbind the frame load event, and remove the frame
		setTimeout( function() {
			pax.event.unbind( frame, 'load' );
			pax.hideStatus();
			pax.util.removeElement( frame ); 
		}, 200 );
		
		//	Run the callback
		//	Note: 	We cannot support "real" JSON responses in the iFrame, (ie: "Content-type: application/x-json"),
		//			as this causes the browser to think we're downloading a file.
		if( args.callBack )args.callBack( null, result, form.action );
	} );

	pax.showStatus();
	form.submit();
};

//	Adds option(s) to target select
pax.form.addOption = function( options, target ) {
	//	Support multiple options
	if( pax.util.getType( options ) != 'array' )options = [options];
	for( var i = 0; i < options.length; i++ ) {
		//	Create a new option here
		target.options[target.options.length] = new Option(options[i].text, options[i].value, false, false);
	}
};

//	Remove an option
pax.form.removeOption = function( options, target ) {
	//	Support multiple options
	if( pax.util.getType( options ) != 'array' )options = [options];
	for (var i = 0; i < options.length; i++) {
		for (var t = 0; t < target.options.length; t++) {
			if( target.options[t] == options[i] )target.options[t] = null;
		}
	}
};


/*	Method: getSelectedOptions
	Gets the selected option(s) from a select box
	
	Parameters:
		selectBox - The select box we're getting the option(s) from

	Example:
		(start code)

			<select name='fruit' id='fruit'>
				<option value="47">Apples</option>
				<option value="49">Oranges</option>
				<option value="67">Pears</option>
			</select>
			[:.
				//pax.form.setSelected( pax.$('fruit'), 'Oranges' );
			:]
		(end)
		This would 

*/
pax.form.getSelectedOptions = function( selectBox ) {
	var result = [];
	for( var i = 0; i < selectBox.options.length; i++ ) {
		if( selectBox.options[i].selected )result.push( selectBox.options[i] );
	}
	return result;
};

//	Returns the options from a select box
pax.form.getOptions = function( selectBox ) {
	var result = [];
	for( var i = 0; i < selectBox.options.length; i++ ) {
		result.push( selectBox.options[i] );
	}
	return result;
};

//	Returns options as an object; this will fail if you have the same value in here twice...
pax.form.getOptionsAsObjectList = function( selectBox ) {
	var options = pax.form.getOptions( selectBox );
	var objList = [];
	for( var i = 0; i < options.length; i++ ) {
		objList.push( { value: options[i].value, text: options[i].text } );
	}
	return objList;
};


/*	Method: setSelected
	Sets the selected index for a select box, by text instead of value
	
	Parameters:
		selectBox - The select box we're setting the value for
		text - which value to set the select box index for

	Example:
		(start code)
			The select box's selected index should be 49, which has a value of 'Oranges': 
			<select name='fruit' id='fruit'>
				<option value="47">Apples</option>
				<option value="49">Oranges</option>
				<option value="67">Pears</option>
			</select>
			[:.
				pax.form.setSelected( pax.$('fruit'), 'Oranges' );
			:]
		(end)
		This would set the select box's selected index value to 49, which has text 'Oranges'

*/
pax.form.setSelected = function( selectBox, text ) {
	if (text == null && selectBox.multiple) {
		for (var i = 0; i < selectBox.options.length; i++) {
			selectBox.options[i].selected = 'selected';
		}
	} else {
		for (var i = 0; i < selectBox.options.length; i++) {
			if (selectBox.multiple) {
				if (text == selectBox.options[i].text)selectBox.options[i].selected = 'selected';
			} else {
				if (text == selectBox.options[i].text) selectBox.selectedIndex = i;
			}
		}
	}
};


/*	Method: setChecked
	Sets the selected index for a check box, by value
	
	Parameters:
		checkBox - The check box we're setting the value for
		value - which value to set the check box index for

	Example:
		Assuming you have a check box set like this:
		(start code)
			<form name='shoppinglist' id='shoppinglist'>
				<input type='checkbox' name='fruit' value='Apple'>Apple
				<input type='checkbox' name='fruit' value='Orange'>Orange
				<input type='checkbox' name='fruit' value='Pear'>Pear
			</form>
			[:.
				pax.form.setChecked( document.forms.shoppinglist.fruit, 'Orange' );
			:]
		(end)
		This would set the check box's selected index to 2, which has a value of 'Orange'

*/
pax.form.setChecked = function( checkBox, value ) {
	for( var i = 0; i < checkBox.length; i++ ) {
		if( value == checkBox[i].value )checkBox[i].checked = true;
	}
};


//	Retreives the value of a field, regardless of type.
pax.form.getFieldValue = function( field ) {

	//	For radio sets, we may get a list, we need to pick the checked one.
	if( pax.util.getType( field ) == 'object' && !field.nodeName ) {
		for( var i = 0; i < field.length; i++ ) {
			if( field[i].checked )return field[i].value;
		}
		return null;
	}

	//	Check field type
	var ft = ( typeof field.nodeName != 'undefined' )? field.nodeName.toLowerCase(): '';
	if( ft == 'input' || ft == 'textarea' )return field.value;
	//	TODO: allow multiple selected options!
	if( ft == 'select' )return field.options[field.selectedIndex].value;
};

//	Select a certain part of the text in a specific text field.
pax.form.selectRange = function( field, start, length ) {
	if( pax.isIe ) {
		var myRange = field.createTextRange();
		myRange.moveStart( "character", start );
		myRange.moveEnd( "character", length );
		myRange.select();
	} else {
		field.setSelectionRange( start, length );
	}
};


/*	Method: checkboxDependents
	This function relates check boxes to eachother, and warns or forces checking, when a Dependency (parent) is being unchecked, and can optionally uncheck Dependents (child) if a parent is unchecked.
	
	Parameters:
		formName - Name of the form the fields exist in
		checkBoxMap - Dictionary that contains the check boxes
		args - An optional object that optionally contain the following:
			* autoCheck - weather to automatically check dependencies (parent) if a dependent (child) is checked. Default is true
			* autoUncheck - weather to automatically uncheck dependents (child) if a dependent (parent) is unchecked. Default is false
			* showHint - weather to show a hint next to a dependent (parent), as it is attempted to be unchecked. Default is true
			* callBack - function to run when a dependency (parent) checkbox is unchecked, parameter is the field.
			
	Note:
		After instanciation, each parent field contains two lists as a properties:
		* childDepend - A list of checkboxes that depend on this field
		* parentDepend - A list of checkboxes that this field depends on
		Also, the reason we have autoCheck and autoUncheck is that you may want to just show a warning, in which case you should disable both. 
		Enabling both autoCheck and autoUncheck is not supported.
		
	TODO: 
		* Currently you need both name and id to access the fields, it shouldn't be like that.
		
	Example:
		(start code)
			<form name='pax.form.checkboxDependents.example3'>
				<ul>
					<li><input type='checkbox' name='box1' id='box1'>box1</li>
					<li><input type='checkbox' name='box2' id='box2'>box2</li>
					<li><input type='checkbox' name='box3' id='box3'>box3</li>
				</ul>
			</form>
			[:.
				var checkBoxMap = {
					'box1':	[],
					'box2':	[ 'box1' ],
					'box3':	[ 'box2' ]
				};
				pax.form.checkboxDependents( 'pax.form.checkboxDependents.example3', checkBoxMap );
			:]
		(end)
		This example uses the default behaviour, it auto checks parent checkboxes, and shows a warning.
		
	Example:
		(start code)
			<form name='pax.form.checkboxDependents.example2'>
				<ul>
					<li><input type='checkbox' name='box1' id='box1'>box1</li>
					<li><input type='checkbox' name='box2' id='box2'>box2</li>
					<li><input type='checkbox' name='box3' id='box3'>box3</li>
				</ul>
			</form>
			[:.
				var checkBoxMap = {
					'box1':	[],
					'box2':	[ 'box1' ],
					'box3':	[ 'box2' ]
				};
				pax.form.checkboxDependents( 'pax.form.checkboxDependents.example2', checkBoxMap, { autoCheck: false, autoUncheck: true } );
			:]
		(end)
		This example auto unchecks the Dependenct (child) boxes. Ie if you uncheck box1, and box2 and box3 are dependents (child), they will be unchecked.
		
	Example:
		(start code)
			<form name='pax.form.checkboxDependents.example1'>
				<ul>
					<li><input type='checkbox' name='pax.js' id='pax.js' >pax.js</li>
					<li><input type='checkbox' name='pax.load.js' id='pax.load.js' >pax.load.js</li>
					<li><input type='checkbox' name='pax.util.js' id='pax.util.js' >pax.util.js</li>
					<li><input type='checkbox' name='pax.cache.js' id='pax.cache.js' >pax.cache.js</li>
					<li><input type='checkbox' name='pax.cache.template.js' id='pax.cache.template.js' >pax.cache.template.js</li>
					<li><input type='checkbox' name='pax.form.js' id='pax.form.js' >pax.form.js</li>
					<li><input type='checkbox' name='pax.fx.js' id='pax.fx.js' >pax.fx.js</li>
					<li><input type='checkbox' name='pax.validate.js' id='pax.validate.js' >pax.validate.js</li>
					<li><input type='checkbox' name='pax.template.js' id='pax.template.js' >pax.template.js</li>
					<li><input type='checkbox' name='pax.template.modifier.js' id='pax.template.modifier.js' >pax.template.modifier.js</li>
					<li><input type='checkbox' name='pax.widget.js' id='pax.widget.js' >pax.widget.js</li>
					<li><input type='checkbox' name='pax.widget.inputbox.js' id='pax.widget.inputbox.js' >pax.widget.inputbox.js</li>
					<li><input type='checkbox' name='pax.window.js' id='pax.window.js' >pax.window.js</li>
				</ul>
			</form>
			[:.
				var checkBoxMap = {
					'pax.cache.template.js': 	[ 'pax.js', 'pax.cache.js', 'pax.template.js' ],
					'pax.template.js': 			[ 'pax.js', 'pax.cache.js', 'pax.cache.template.js', 'pax.template.modifier.js' ],
					'pax.template.modifier.js': [ 'pax.template.js' ],
					'pax.widget.js': 			[ 'pax.js', 'pax.util.js' ],
					'pax.validate.js': 			[ 'pax.js', 'pax.util.js' ],
					'pax.fx.js': 				[ 'pax.js', 'pax.util.js' ]
				};
				pax.form.checkboxDependents( 'pax.form.checkboxDependents.example1', checkBoxMap );
			:]
		(end)
		This example auto unchecks the dependent (child) boxes. Ie if you uncheck a box that has Dependents, it will uncheck the Dependents. The example has circular references, ie one or more boxes that depend on eachother.
		
	Example:
		(start code)
			<form name='pax.form.checkboxDependents.example4'>
				<ul>
					<li><input type='checkbox' name='box1' id='box1'>box1</li>
					<li><input type='checkbox' name='box2' id='box2'>box2</li>
					<li><input type='checkbox' name='box3' id='box3'>box3</li>
				</ul>
			</form>
			[:.
				var callBack = function( field ) {
					var message = '';
					for( var i = 0; i < field.parentDepend.length; i++ ) {
						message += 'Dependent: ' + field.parentDepend[i];
					}
					message += '<b'+'r>' + field.childDepend;
					pax.box.showOnRight( 'fieldMessage', message, 'hintMessage', field, 5 );
				};
				
				var checkBoxMap = {
					'box1':	[],
					'box2':	[ 'box1' ],
					'box3':	[ 'box2' ]
				};
				pax.form.checkboxDependents( 'pax.form.checkboxDependents.example4', checkBoxMap, { autoUncheck: true, showHint: false, callBack: callBack } );
			:]
		(end)
		This example auto unchecks the dependent (child) boxes. Ie if you uncheck a box that has Dependents, it will uncheck the Dependents. The example has also tuned off the hint box.
		
*/
pax.form.checkboxDependents = function( formName, checkBoxMap, args ) {
	args = ( typeof( args ) != 'undefined' )? args: {};
	var autoCheck = ( typeof( args.autoCheck ) != 'undefined' )?args.autoCheck : true;
	var autoUncheck = ( typeof( args.autoUncheck ) != 'undefined' )?args.autoUncheck : false;
	var showHint = ( typeof( args.showHint ) != 'undefined' )?args.showHint : true;
	var callBack = ( typeof( args.callBack ) != 'undefined' )?args.callBack : null;
	
	//	"Normalise" the checkBoxMap. Ie: resolve cyclic Dependencies by consolidating each cyclic Dependency into one list per entry
	for( var c in checkBoxMap ) {
		var newDeps = [];
		for( var dep in checkBoxMap[c] ) {
			if( pax.util.hasKey( checkBoxMap[c][dep], checkBoxMap ) ) {
				newDeps = pax.util.joinObj( checkBoxMap[c], checkBoxMap[checkBoxMap[c][dep]], false );
			} else {
				newDeps[newDeps.length] = checkBoxMap[c][dep];
			}
		}
		if( newDeps != [] )checkBoxMap[c] = newDeps;
	}
	
	//	Bind the click events
	for( var c in checkBoxMap ) {
		pax.$(c).childDepend = checkBoxMap[c];
		
		//	Dependency (parent) checkboxes
		pax.event.bind( pax.$(c), 'click', function() {
			if( this.checked ) {
				var myFields = pax.validate.allFields( formName, true );
				for( var f = 0; f < myFields.length; f++ ) {
					field = myFields[f];
					if( field.type == 'checkbox' ) {
						var checkIt = false;
						for( var i = 0; i < this.childDepend.length; i++ ) {
							if( field.name == this.childDepend[i] )checkIt = true;
						}
						if( checkIt ) {
							//	Automatically check parent dependent if a child is checked.
							if( autoCheck )field.checked = true;
						}
					}
				}
			}
		} );
		

		//	Warning function to check each child from the parent.
		var warningFunc = function() {
			var canUncheck = true;
			var deps = "";
			for( var dep in this.parentDepend ) {
				var myDep = this.parentDepend[dep];
				if( pax.$(myDep).checked && pax.$(myDep) != this ) {
					canUncheck = false;
					deps += myDep + " ";
					if( ! this.checked ) {
						if( autoUncheck )pax.$(myDep).checked = false;
					}
				}
			}
			if( !canUncheck ) {
				if( !this.checked ) {
					//	Forces parent to stay checked
					if( autoCheck )this.checked = true;
					if( showHint )pax.box.showOnRight( 'Dependents', "This field should not be unchecked, as it is required by: " + deps, 'hintMessage', this, 5 );
					if( callBack )callBack( this );
				}
			}
		};

		//	Dependents (child) boxes
		for( var d in checkBoxMap[c] ) {
			var myBox = pax.$( checkBoxMap[c][d] );
			if( typeof( myBox.parentDepend ) == 'undefined' )myBox.parentDepend = [c];
			else if( ! pax.util.arrayHasValue( c, myBox.parentDepend ) )myBox.parentDepend.push( c );

			//	Bind warning function to each parent, to check each child
			pax.event.bind( myBox, 'click', warningFunc );
		}
	}
};
